﻿using System;
using System.Reflection;

namespace BizElements.BusinessLayer
{
    /// <summary>Executes remote method calls on received objects.</summary>
    public class MethodReceiverService : IMethodReceiverService
    {
        /// <summary>Invokes a method.</summary>
        /// <param name="serializedBusinessObject">Binary serialized object whose definition (class) contains the specified method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="serializedMethodParameters">Binary serialized method parameters.</param>
        /// <returns>Changed object serialized with binary formatter.</returns>
        public virtual byte[] InvokeMethod(ref byte[] serializedBusinessObject, string methodName, byte[][] serializedMethodParameters)
        {
            try
            {
                object businessObject = Serializer.DeserializeWithBinaryFormater(serializedBusinessObject);
                object[] methodParameters = Serializer.DeserializeWithBinaryFormater(serializedMethodParameters);
                if (businessObject == null)
                    throw new Exception("Cannot deserialize business object. Make sure you have referenced object's assembly.");

                object retval = ReflectionUtil.InvokeMethod(businessObject, methodName, methodParameters);

                serializedBusinessObject = Serializer.SerializeWithBinaryFormater(businessObject);
                byte[] serializedRetval = Serializer.SerializeWithBinaryFormater(retval);
                return serializedRetval;
            }
            catch (TargetInvocationException e)
            {
                LogError(e.InnerException);
                throw e.InnerException;
            }
            catch (Exception e)
            {
                LogError(e);
                throw;
            }
        }

        /// <summary>Invokes a static method.</summary>
        /// <param name="serializedType">Binary serialized type which contains the specified static method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="serializedMethodParameters">Binary serialized method parameters.</param>
        /// <returns>Method's return value serialized with binary formatter.</returns>
        public virtual byte[] InvokeStaticMethod(byte[] serializedType, string methodName, byte[][] serializedMethodParameters)
        {
            try
            {
                Type type = (Type)Serializer.DeserializeWithBinaryFormater(serializedType);
                object[] methodParameters = Serializer.DeserializeWithBinaryFormater(serializedMethodParameters);
                object retval = ReflectionUtil.InvokeStaticMethod(type, methodName, methodParameters);
                byte[] serializedRetval = Serializer.SerializeWithBinaryFormater(retval);
                return serializedRetval;
            }
            catch (TargetInvocationException e)
            {
                LogError(e.InnerException);
                throw e.InnerException;
            }
            catch (Exception e)
            {
                LogError(e);
                throw;
            }
        }

        /// <summary>Invokes a generic method.</summary>
        /// <param name="serializedBusinessObject">Binary serialized object whose definition (class) contains the specified method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="serializedMethodParameters">Binary serialized method parameters.</param>
        /// <returns>Changed object serialized with binary formatter.</returns>
        public virtual byte[] InvokeGenericMethod(ref byte[] serializedBusinessObject, string methodName, byte[][] serializedMethodParameters)
        {
            try
            {
                object businessObject = Serializer.DeserializeWithBinaryFormater(serializedBusinessObject);
                object[] methodParameters = Serializer.DeserializeWithBinaryFormater(serializedMethodParameters);
                if (businessObject == null)
                    throw new Exception("Cannot deserialize business object. Make sure you have referenced object's assembly.");

                object retval = ReflectionUtil.InvokeGenericMethod(businessObject, methodName, methodParameters);

                serializedBusinessObject = Serializer.SerializeWithBinaryFormater(businessObject);
                byte[] serializedRetval = Serializer.SerializeWithBinaryFormater(retval);
                return serializedRetval;
            }
            catch (TargetInvocationException e)
            {
                LogError(e.InnerException);
                throw e.InnerException;
            }
            catch (Exception e)
            {
                LogError(e);
                throw;
            }
        }

        /// <summary>Invokes a generic static method.</summary>
        /// <param name="serializedType">Binary serialized type which contains the specified static method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="serializedMethodParameters">Binary serialized method parameters.</param>
        /// <returns>Method's return value serialized with binary formatter.</returns>
        public virtual byte[] InvokeGenericStaticMethod(byte[] serializedType, string methodName, byte[][] serializedMethodParameters)
        {
            try
            {
                Type type = (Type)Serializer.DeserializeWithBinaryFormater(serializedType);
                object[] methodParameters = Serializer.DeserializeWithBinaryFormater(serializedMethodParameters);
                object retval = ReflectionUtil.InvokeGenericStaticMethod(type, methodName, methodParameters);
                byte[] serializedRetval = Serializer.SerializeWithBinaryFormater(retval);
                return serializedRetval;
            }
            catch (TargetInvocationException e)
            {
                LogError(e.InnerException);
                throw e.InnerException;
            }
            catch (Exception e)
            {
                LogError(e);
                throw;
            }
        }

        internal static void LogError(Exception error)
        {
            if (error is TargetInvocationException)
            {
                TargetInvocationException targetInvocationError = (TargetInvocationException)error;
                if (targetInvocationError.InnerException != null)
                    error = targetInvocationError.InnerException;
            }

            bool isCritical = false;
            if (error is System.Data.SqlClient.SqlException)
                isCritical = (error as System.Data.SqlClient.SqlException).Class >= 17;
            else if (error is System.Net.WebException)
                isCritical = true;

            if (isCritical)
                Log.CriticalAppError(error);
            else
                Log.AppError(error);
        }
    }
}
